#include "preHeader.h"
#include "LootMgr.h"
#include "Object/Player.h"

LootMgr::LootMgr()
{
}

LootMgr::~LootMgr()
{
}

bool LootMgr::LoadLootRelation()
{
	auto pLSTbl = sDBMgr.GetTable<LootSet>();
	auto pLSGTbl = sDBMgr.GetTable<LootSetGroup>();
	auto pLSGITbl = sDBMgr.GetTable<LootSetGroupItem>();
	auto pLSGCTbl = sDBMgr.GetTable<LootSetGroupCheque>();
	for (auto itr = pLSTbl->Begin(); itr != pLSTbl->End(); ++itr) {
		auto& lsInfo = itr->second;
		LootSetPrototype lsPt;
		lsPt.lsInfo = &lsInfo;
		m_lsList.emplace(lsInfo.lsID, lsPt);
	}
	for (auto itr = pLSGTbl->Begin(); itr != pLSGTbl->End(); ++itr) {
		auto& lsgInfo = itr->second;
		auto lsItr = m_lsList.find(lsgInfo.lsID);
		if (lsItr == m_lsList.end()) {
			continue;
		}
		auto& lsgList = lsItr->second.lsgList;
		LootSetGroupPrototype lsgPt;
		lsgPt.lsgInfo = &lsgInfo;
		lsgList.push_back(lsgPt);
	}
	for (auto itr = pLSGITbl->Begin(); itr != pLSGITbl->End(); ++itr) {
		auto& lsgiInfo = itr->second;
		auto lsItr = m_lsList.find(lsgiInfo.lsID);
		if (lsItr == m_lsList.end()) {
			continue;
		}
		auto& lsgList = lsItr->second.lsgList;
		auto lgsItr = std::find_if(std::begin(lsgList), std::end(lsgList),
			[&lsgiInfo](const LootSetGroupPrototype& lsgPt) {
			return lsgPt.lsgInfo->lsgID == lsgiInfo.lsgID;
		});
		if (lgsItr == lsgList.end()) {
			continue;
		}
		auto& lsgiList = lgsItr->lsgiList;
		LootSetGroupItemPrototype lsgiPt;
		lsgiPt.pItemProto = GetDBEntry<ItemPrototype>(lsgiInfo.lsgiItemTypeID);
		lsgiPt.lsgiInfo = &lsgiInfo;
		if (lsgiPt.pItemProto == NULL) {
			continue;
		}
		lsgiList.push_back(lsgiPt);
	}
	for (auto itr = pLSGCTbl->Begin(); itr != pLSGCTbl->End(); ++itr) {
		auto& lsgcInfo = itr->second;
		auto lsItr = m_lsList.find(lsgcInfo.lsID);
		if (lsItr == m_lsList.end()) {
			continue;
		}
		auto& lsgList = lsItr->second.lsgList;
		auto lgsItr = std::find_if(std::begin(lsgList), std::end(lsgList),
			[&lsgcInfo](const LootSetGroupPrototype& lsgPt) {
			return lsgPt.lsgInfo->lsgID == lsgcInfo.lsgID;
		});
		if (lgsItr == lsgList.end()) {
			continue;
		}
		auto& lsgcList = lgsItr->lsgcList;
		LootSetGroupChequePrototype lsgcPt;
		lsgcPt.lsgcInfo = &lsgcInfo;
		lsgcList.push_back(lsgcPt);
	}
	return true;
}

const LootSetPrototype* LootMgr::GetLootSetPrototype(uint32 lootSetID) const
{
	auto itr = m_lsList.find(lootSetID);
	return itr != m_lsList.end() ? &itr->second : NULL;
}

bool LootMgr::IsPlayerRequireLoot4Quest(
	Player *pPlayer, uint32 lootSetID, uint32 questTypeID) const
{
	DBGASSERT(questTypeID != 0);
	const LootSetPrototype* pLSProto = GetLootSetPrototype(lootSetID);
	if (pLSProto == NULL) {
		return false;
	}
	auto pQuestStorage = pPlayer->GetQuestStorage();
	for (const LootSetGroupPrototype& lsgPt : pLSProto->lsgList) {
		for (const LootSetGroupItemPrototype& lsgiPt : lsgPt.lsgiList) {
			const LootSetGroupItem& lsgiInfo = *lsgiPt.lsgiInfo;
			if (!CanLootItem4Player(pPlayer, questTypeID, lsgiInfo)) {
				continue;
			}
			auto lackItemCnt = pQuestStorage->GetQuestLackItemCount(
				questTypeID, lsgiInfo.lsgiItemTypeID);
			if (lackItemCnt != 0) {
				return true;
			}
		}
	}
	return false;
}

bool LootMgr::IsEmptyLootPrizes(const LootPrizes& prizes)
{
	for (auto& lootItem : prizes.lootItems) {
		if (!lootItem.isPicked) {
			return false;
		}
	}
	for (auto& lootCheque : prizes.lootCheques) {
		if (!lootCheque.isPicked) {
			return false;
		}
	}
	return true;
}

uint32 LootMgr::GetFillItemsLackSlotCount4LootItems(Player* pPlayer,
	const std::vector<LootItem>& lootItems, bool canMergeable)
{
	std::vector<const ItemPrototype*> pItemProtos(lootItems.size());
	std::vector<inst_item_prop> itemProps(lootItems.size());
	for (size_t i = 0, n = lootItems.size(); i < n; ++i) {
		const LootItem& lootItem = lootItems[i];
		pItemProtos[i] = lootItem.pItemProto;
		itemProps[i] = AssignLootItemProp(lootItem);
	}
	return pPlayer->GetItemStorage()->GetFillItemsLackSlotCount(
		pItemProtos.data(), itemProps.data(), itemProps.size(), canMergeable);
}

void LootMgr::CreateAddItems4LootItems(
	Player* pPlayer, const std::vector<LootItem>& lootItems,
	ITEM_FLOW_TYPE flowType, params<uint32> flowParams, bool isSendPopMsg,
	Item** pNewItemPtr)
{
	std::vector<const ItemPrototype*> pItemProtos(lootItems.size());
	std::vector<inst_item_prop> itemProps(lootItems.size());
	for (size_t i = 0, n = lootItems.size(); i < n; ++i) {
		const LootItem& lootItem = lootItems[i];
		pItemProtos[i] = lootItem.pItemProto;
		itemProps[i] = AssignLootItemProp(lootItem);
	}
	pPlayer->GetItemStorage()->CreateAddItemsMail(
		pItemProtos.data(), itemProps.data(), itemProps.size(),
		flowType, flowParams, isSendPopMsg, pNewItemPtr);
}

inst_item_prop LootMgr::AssignLootItemProp(const LootItem& lootItem)
{
	inst_item_prop itemProp;
	itemProp.itemTypeID = lootItem.pItemProto->itemTypeID;
	itemProp.itemCount = lootItem.itemCount;
	itemProp.itemOwner = lootItem.isBindToPicker ? 1 : 0;
	return itemProp;
}

LootPrizes LootMgr::GeneratePrizes(
	Player* pPlayer, uint32 lootSetID, uint32 questTypeID) const
{
	LootPrizes prizes;
	const LootSetPrototype* pLSProto = GetLootSetPrototype(lootSetID);
	if (pLSProto == NULL) {
		return prizes;
	}

	for (const LootSetGroupPrototype& lsgPt : pLSProto->lsgList) {
		const LootSetGroup& lsgInfo = *lsgPt.lsgInfo;
		if (1 > lsgInfo.lsgOdds && System::Randf(0, 1) > lsgInfo.lsgOdds) {
			continue;
		}
		const uint32 lootItemTimes = System::Randi(
			lsgInfo.lsgMinItemTimes, lsgInfo.lsgMaxItemTimes, true);
		if (lootItemTimes != 0) {
			GenerateItemPrizes(pPlayer,
				lootItemTimes, lsgPt.lsgiList, prizes.lootItems, questTypeID);
		}
		const uint32 lootChequeTimes = System::Randi(
			lsgInfo.lsgMinChequeTimes, lsgInfo.lsgMaxChequeTimes, true);
		if (lootChequeTimes != 0) {
			GenerateChequePrizes(
				pPlayer, lootChequeTimes, lsgPt.lsgcList, prizes.lootCheques);
		}
	}

	return prizes;
}

void LootMgr::GenerateItemPrizes(Player* pPlayer, uint32 lootTimes,
	const std::vector<LootSetGroupItemPrototype>& lsgiList,
	std::vector<LootItem>& lootItems, uint32 questTypeID)
{
	std::vector<uint32> lootedItemTimes(lsgiList.size());
	std::vector<uint32> lootItemWeight(lsgiList.size());
	auto CalcAllLootItemWeight = [&, pPlayer]() {
		for (size_t i = 0, n = lsgiList.size(); i < n; ++i) {
			lootItemWeight[i] = CalcLootItemWeight(pPlayer,
				questTypeID, lsgiList[i], lootedItemTimes[i], lootItems);
		}
	};

	for (uint32 i = 0; i < lootTimes; ++i) {
		CalcAllLootItemWeight();
		auto sltIdx = RandomByWeightValue<int>(
			lootItemWeight.data(), lootItemWeight.size());
		if (sltIdx != 0 || lootItemWeight[0] != 0) {
			AppendLootItem(lootItems, NewLootItem(lsgiList[sltIdx]));
			lootedItemTimes[sltIdx] += 1;
		} else {
			break;
		}
	}
}

uint32 LootMgr::CalcLootItemWeight(Player* pPlayer, uint32 questTypeID,
	const LootSetGroupItemPrototype& lsgiPt, uint32 lootedTimes,
	const std::vector<LootItem>& lootedItems)
{
	const LootSetGroupItem& lsgiInfo = *lsgiPt.lsgiInfo;
	if (lsgiInfo.lsgiMaxTimes <= lootedTimes) {
		return 0;
	}
	if (!CanLootItem4Player(pPlayer, questTypeID, lsgiInfo)) {
		return 0;
	}
	if (lsgiInfo.lsgiLimitQuest != 0) {
		auto lackItemCnt = pPlayer->GetQuestStorage()->GetQuestLackItemCount(
			lsgiInfo.lsgiLimitQuest, lsgiInfo.lsgiItemTypeID);
		auto lootedItemCnt =
			GetLootedItemCount(lootedItems, lsgiInfo.lsgiItemTypeID);
		if (lackItemCnt <= lootedItemCnt) {
			return 0;
		}
	}
	return lsgiInfo.lsgiWeight;
}

LootItem LootMgr::NewLootItem(const LootSetGroupItemPrototype& lsgiPt)
{
	const LootSetGroupItem& lsgiInfo = *lsgiPt.lsgiInfo;
	LootItem lootItem;
	lootItem.pItemProto = lsgiPt.pItemProto;
	lootItem.itemCount = System::Randi(
		lsgiInfo.lsgiMinCount, lsgiInfo.lsgiMaxCount, true);
	lootItem.isBindToPicker = lsgiInfo.lsgiFlags.bindToPicker;
	return lootItem;
}

void LootMgr::AppendLootItem(
	std::vector<LootItem>& lootItems, LootItem&& lootItem)
{
	auto itr = std::find_if(lootItems.begin(), lootItems.end(),
		[&lootItem](const LootItem& tgtLootItem) {
		return CanMergeLootItem(lootItem, tgtLootItem);
	});
	if (itr != lootItems.end()) {
		itr->itemCount += lootItem.itemCount;
	} else {
		lootItems.push_back(std::move(lootItem));
	}
}

bool LootMgr::CanMergeLootItem(
	const LootItem& lootItem1, const LootItem& lootItem2)
{
	if (lootItem1.pItemProto != lootItem2.pItemProto) {
		return false;
	}
	if (lootItem1.isBindToPicker != lootItem2.isBindToPicker) {
		return false;
	}
	return true;
}

uint32 LootMgr::GetLootedItemCount(
	const std::vector<LootItem>& lootedItems, uint32 itemTypeID)
{
	uint32 lootedCount = 0;
	for (const auto& lootedItem : lootedItems) {
		if (lootedItem.pItemProto->itemTypeID == itemTypeID) {
			lootedCount += lootedItem.itemCount;
		}
	}
	return lootedCount;
}

bool LootMgr::CanLootItem4Player(Player* pPlayer, uint32 questTypeID,
	const LootSetGroupItem& lsgiInfo)
{
	if (lsgiInfo.lsgiLimitCareer != 0) {
		if (lsgiInfo.lsgiLimitCareer != pPlayer->GetCareer()) {
			return false;
		}
	}
	if (lsgiInfo.lsgiLimitGender != 0) {
		if (lsgiInfo.lsgiLimitGender != pPlayer->GetGender()) {
			return false;
		}
	}
	if (lsgiInfo.lsgiLimitQuest != 0 && questTypeID != 0) {
		if (lsgiInfo.lsgiLimitCareer != questTypeID) {
			return false;
		}
	}
	return true;
}

void LootMgr::GenerateChequePrizes(Player* pPlayer, uint32 lootTimes,
	const std::vector<LootSetGroupChequePrototype>& lsgcList,
	std::vector<LootCheque>& lootCheques)
{
	std::vector<uint32> lootedChequeTimes(lsgcList.size());
	std::vector<uint32> lootChequeWeight(lsgcList.size());
	auto CalcAllLootChequeWeight = [&, pPlayer]() {
		for (size_t i = 0, n = lsgcList.size(); i < n; ++i) {
			lootChequeWeight[i] = CalcLootChequeWeight(
				pPlayer, lsgcList[i], lootedChequeTimes[i]);
		}
	};

	for (uint32 i = 0; i < lootTimes; ++i) {
		CalcAllLootChequeWeight();
		auto sltIdx = RandomByWeightValue<int>(
			lootChequeWeight.data(), lootChequeWeight.size());
		if (sltIdx != 0 || lootChequeWeight[0] != 0) {
			AppendLootCheque(lootCheques, NewLootCheque(lsgcList[sltIdx]));
			lootedChequeTimes[sltIdx] += 1;
		} else {
			break;
		}
	}
}

uint32 LootMgr::CalcLootChequeWeight(Player* pPlayer,
	const LootSetGroupChequePrototype& lsgcPt, uint32 lootedTimes)
{
	const LootSetGroupCheque& lsgcInfo = *lsgcPt.lsgcInfo;
	if (lsgcInfo.lsgcMaxTimes <= lootedTimes) {
		return 0;
	}
	return lsgcInfo.lsgcWeight;
}

LootCheque LootMgr::NewLootCheque(const LootSetGroupChequePrototype& lsgcPt)
{
	const LootSetGroupCheque& lsgcInfo = *lsgcPt.lsgcInfo;
	LootCheque lootCheque;
	lootCheque.chequeType = lsgcInfo.lsgcChequeType;
	lootCheque.chequeValue = System::RandS64(
		lsgcInfo.lsgcMinValue, lsgcInfo.lsgcMaxValue, true);
	return lootCheque;
}

void LootMgr::AppendLootCheque(
	std::vector<LootCheque>& lootCheques, LootCheque&& lootCheque)
{
	auto itr = std::find_if(lootCheques.begin(), lootCheques.end(),
		[&lootCheque](const LootCheque& tgtLootCheque) {
		return CanMergeLootCheque(lootCheque, tgtLootCheque);
	});
	if (itr != lootCheques.end()) {
		itr->chequeValue += lootCheque.chequeValue;
	} else {
		lootCheques.push_back(std::move(lootCheque));
	}
}

bool LootMgr::CanMergeLootCheque(
	const LootCheque& lootCheque1, const LootCheque& lootCheque2)
{
	if (lootCheque1.chequeType != lootCheque2.chequeType) {
		return false;
	}
	return true;
}

void LootMgr::MergePrizes(LootPrizes& prizes, LootPrizes&& tempPrizes)
{
	for (auto& lootItem : tempPrizes.lootItems) {
		AppendLootItem(prizes.lootItems, std::move(lootItem));
	}
	for (auto& lootCheque : tempPrizes.lootCheques) {
		AppendLootCheque(prizes.lootCheques, std::move(lootCheque));
	}
}
